﻿using System;
// 假定S是B的子类，如果X<S>允许引用转换成X<B>，那么称X为协变类。
namespace _075_协变
{
    class Animal { }
    class Bear : Animal{ }
    class Camel : Animal { }
    public class Stack<T>: IPoppable<T>//实现IPoppable<T>对应：：：下面
    {
        int position;
        T[] data = new T[100];
        public void Push(T obj) { data[position++] = obj; }
        public T Pop() { return data[--position]; }
    }

    //②但是，协变性的缺失可能妨碍复用性。
    //假定下例中，我们相写一个Wash方法来操作整个Animal栈：
    /*public class ZonCleaner
    {
        public static void Wash(Stack<Animal> animals) { }
    }*/
    //③如果调用Wash方法操作Bear栈，将会导致编译时错误。
    //解决方法时，重新定义一个带有约束的Wash方法：
    class ZooCleaner
    {
        public static void Wash<T>(Stack<T> animals) where T : Animal { }
    }
    
    class Program
    {
        static void Main(string[] args)
        {
            Stack<Bear> bears = new Stack<Bear>();
            //Stack<Animal> animals = bears; // 编译时错误

            //①这个限制避免了下面代码发生运行时错误：
            //animals.Push(new Camel()); // 视图把Camel类加入Bear栈


            //④这样，我们就可以用如下方法调用Wash了：
            Stack<Bear> bears1 = new Stack<Bear>();
            ZooCleaner.Wash(bears1);
            //⑤另一种解决方法时让Stack<T>实现一个协变的泛化接口。


            // 2.数值
            // 由于历史原因，数组array类型具有协变性。
            // 这表明如果B是A的子类（A和B都是引用类型），那么B[]可以转换成A[]。
            Bear[] bears2 = new Bear[3];
            Animal[] animals2 = bears2; // 正确
                                        // 这种可复用性的缺点在于，指定元素值可能导致运行时错误：
                                        //animals2[0] = new Camel(); // 运行时错误


            //3.接口② T前的out修饰符是C#4.0的新特性，表明T只用在输出的位置（例如：方法的返回值）。out修饰符将接口标识为具有协变性的并允许以下操作：
            var bears32 = new Stack<Bear>();
            bears32.Push(new Bear());
            //Bear实现IPoppable<Bear>。我们把它转换成IPoppable<Animal>
            IPoppable<Animal> animals32 = bears32; // 合法 //实现IPoppable<T>对应：：：上面
            //Animal a32 = animals32.Pop();
            //基于接口有协变性的优点，将bear转换成animals是编译器允许的。它是类型安全的，因为编译器试图避免发生Camel类进栈，因为T仅能在输出的位置出现，所以不可能将Camel类输入接口。


        }
    }
    // 3.接口①
    // 在C#4.0中，泛化接口对用out修饰符标注的类型参数支持协变。和数组不同，out修饰符保证了协变性的接口是完全类型安全的。
    //为了阐述这一点，假定Stack类实现了下面的接口：
    public interface IPoppable<out T> { T Pop(); }
}